﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using BombermanExcepciones;

namespace Bomberman
{
	[Serializable]
	public class Mapa
	{
		private const int vidaObstaculoLadrillo = 5;
		private const int vidaObstaculoCemento = 10;
		private const int vidaObstaculoAcero = Int32.MaxValue;
		private const int PorcentajeMaximoObstaculos = 60;
		private bool terminoPartida = false;
		private int dimensionX;
		private int dimensionY;
		Casillero[,] matrizCasilleros;


		public Mapa(int dimensionX = 0 ,int dimensionY = 0)
		{
			this.dimensionX = dimensionX;
			this.dimensionY = dimensionY;
			matrizCasilleros = new Casillero[dimensionX, dimensionY];
			InicializarMapa(dimensionX, dimensionY);
		}

		private void InicializarMapa(int dimensionX, int dimensionY)
		{
			for (int i = 0; i < dimensionX; i++)
			{
				for (int j = 0; j < dimensionY; j++)

					matrizCasilleros[i, j] = new Casillero(i, j);

			}

			// Conecto todos los casilleros entre si para poder tratar al mapa como un grafo.
			RealizarConexiones();
		}
        

		public void GenerarMapa(int porcentajeObstaculos)
		{
			if (porcentajeObstaculos > PorcentajeMaximoObstaculos)
			{
				throw new DemasiadosObstaculosException();
			}

			int cantidadObstaculos = (porcentajeObstaculos * (DimencionX * DimencionY)) / 100;
			List<Obstaculo> obstaculos = new List<Obstaculo>();

			Dictionary<String,int> claseDeObstaculos = new Dictionary<String,int>();

			Dictionary<Tuple<int, int>, Obstaculo> posicionesXY; // La lista interior tiene la pos x e y.
			Random tipoObstaculoRandom = new Random();

			claseDeObstaculos["Bloque Ladrillos"] = vidaObstaculoLadrillo;
			claseDeObstaculos["Bloque Cemento"] = vidaObstaculoCemento;
			claseDeObstaculos["Bloque Acero"] = vidaObstaculoAcero;

			posicionesXY = this.GenerarPosicones( cantidadObstaculos );

			foreach (Tuple<int, int> posicion in posicionesXY.Keys)
			{   
				int clase = tipoObstaculoRandom.Next(claseDeObstaculos.Count);
				Obstaculo obstaculo = new Obstaculo(claseDeObstaculos[claseDeObstaculos.Keys.ElementAt<string>(clase)], claseDeObstaculos.Keys.ElementAt<string>(clase));	
				obstaculo.Posicionar(matrizCasilleros[posicion.Item1,posicion.Item2]);
				obstaculos.Add(obstaculo);
			}

			AgregarArticulos(cantidadObstaculos, obstaculos);
		}

		private void AgregarArticulos(int cantidadObstaculos, List<Obstaculo> obstaculos)
		{
			Random r = new Random();
			int max = r.Next(cantidadObstaculos/15);
			Dictionary<int,Interactuable> agregados = new Dictionary<int,Interactuable>();

			int indice = r.Next(cantidadObstaculos);
			PuertaNivel nuevaPuerta = new PuertaNivel(this);
			agregados.Add(indice,nuevaPuerta);
			obstaculos[indice].Ocultar(new PuertaNivel(this));
			
			AgregarTipoArticulos(cantidadObstaculos, obstaculos, agregados, Articulo.MejorarBombaArticulo);
			
			AgregarTipoArticulos(cantidadObstaculos, obstaculos, agregados, Articulo.MejoraRetardoBombasArticulo);
				
			AgregarTipoArticulos(cantidadObstaculos, obstaculos, agregados,Articulo.MejorarVelocidadArticulo);
			
		}

		private void AgregarTipoArticulos(int cantidadObstaculos, List<Obstaculo> obstaculos, Dictionary<int, Interactuable> agregados, String tipoArticulo)
		{
			Random r = new Random();
			int max = r.Next(cantidadObstaculos / 15);
			int indice = r.Next(cantidadObstaculos);
			int contadorWatchDog = 0;
			
			for (int i = 0; i < max + 1; i++)
			{

				while (agregados.ContainsKey(indice) || (obstaculos[indice].ObtenerNombre() == "Bloque Acero"))
				{
					contadorWatchDog++;
					if (contadorWatchDog > 10000) return;	// Si no encuentra una casilla como la buscada pasadas muchas iteraciones sale.
					indice = r.Next(cantidadObstaculos);
				}
				Articulo nuevoArticulo = new Articulo(tipoArticulo);
				agregados.Add(indice, nuevoArticulo);
				obstaculos[indice].Ocultar(nuevoArticulo);
			}
		}

		public void TerminarPartida()
		{
			terminoPartida = true;
		}

		public bool TerminoPartida()
		{
			return terminoPartida;
		}

		private Dictionary<Tuple<int,int>,Obstaculo> GenerarPosicones(int cantidad)
		{
			Random random = new Random();
			Dictionary<Tuple<int, int>, Obstaculo> posiciones = new Dictionary<Tuple<int, int>, Obstaculo>();

			for (int i = 0; i < cantidad; i++)
			{
				Tuple<int, int> posicion;
				while (true)
				{
					int posX = (random.Next(this.DimencionX - 1));
					int posY = (random.Next(this.DimencionY - 1));
					posicion = new Tuple<int, int>(posX,posY);
					if(!posiciones.ContainsKey(posicion)) break;      // Para que no se repitan las posible posiciones
				}

				posiciones.Add(posicion,null);                          
			}
			
			return posiciones;
		}

		// Metodo que recorre el mapa conectando cada casillero con sus casilleros adyacentes
		private void RealizarConexiones()
		{
			for (int i=0; i<this.dimensionX; i++)
				for (int j=0; j<this.dimensionY;j++)
					ConectarCasillero(i,j);
		}


		//Metodo que conecta un casillero con sus casilleros adyacentes.
		private void ConectarCasillero(int x, int y)
		{

			Casillero casilleroActual = this.VerCasillero(x, y);
			Casillero casilleroSuperior = this.VerCasillero (x, y - 1);
			Casillero casilleroInferior = this.VerCasillero (x, y + 1);
			Casillero casilleroDerecha = this.VerCasillero (x + 1, y);
			Casillero casilleroIzquierda = this.VerCasillero(x - 1, y);

			casilleroActual.AsignarCasillerosAdyacentes(casilleroSuperior,casilleroInferior,casilleroIzquierda,casilleroDerecha);
			
		}

	         
		public Casillero VerCasillero(int posicionX, int posicionY)
		{
            
			if (!this.RangoValido(posicionX,posicionY))
				return null;
		
			return matrizCasilleros[posicionX, posicionY];
		}


		public bool RangoValido(int posicionX, int posicionY)
		{

			bool valido = true;

			if ( (posicionX < 0) || (posicionX >= this.dimensionX) )
			{
				valido = false;
			} else
			{
				if ( (posicionY < 0) || (posicionY >= this.dimensionY) )
					valido = false;
			}

			return valido;
		}

		public int DimencionX 
		{
			get { return dimensionX; }
		}

		public int DimencionY
		{
			get { return dimensionY; }
		}

		public int CantidadDeCasillero()
		{
			return dimensionX*dimensionY;
		}

	}
}
